<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        function diff(oldTree, newTree) {
            let index = 0; // 当前节点的标志(树形层数)
            let patches = [] // 用来记录每个节点差异的对象
            dfsWalk(oldTree, newTree, patches, index); // 进行深度优先遍历
            return patches;
        }

        import { isVNode, isVText, isArray } from './utils/type.js'

        // 对两棵树进行深度优先遍历
        function dfsWalk(oldNode, newNode, patches, index) {

            if (newNode === oldNode) {
                return
            }

            let patch = patches[index];

            if (!oldNode) {
                // 旧节点不存在，直接插入
                // appendPatch 是用来存节点之间差异的
                patch = appendPatch(patch, {
                    type: PATCH.INSERT,
                    vNode: newNode,
                })
            } else if (!newNode) {
                // 新节点不存在，删除旧节点
                patch = appendPatch(patch, {
                    type: PATCH.REMOVE,
                    vNode: null
                })
            } else if (isVNode(newNode)) { // 新节点是 VNode，就相当于前面写的Element
                if (isVNode(oldNode)) { // 旧节点也是 VNode，就要比较这两个节点的 tagName是否一致
                    // 新旧节点 tagName 一致，并且 key 也一致。
                    if (newNode.tagName === oldNode.tagName && newNode.key === oldNode.key) {
                        // 新老节点属性的对比, diffProps方法就是对新旧节点自身属性的对比
                        // 属性如果有差异，propsPatch的长度 > 0,且差异存在 propsPatch中
                        const propsPatch = diffProps(newNode.props, oldNode.props)
                        if (propsPatch && propsPatch.length > 0) {
                            patch = appendPatch(patch, {
                                type: PATCH.PROPS, // props这个表示是节点的属性差异
                                patches: propsPatch // 这里存的是差异的内容
                            })
                        }
                        // 新老节点子节点的对比
                        // diffChildren 方法是专门来对比子节点的。
                        patch = diffChildren(oldNode, newNode, patches, patch, index)
                    }
                } else {
                    // 旧节点不是 VNode， 新节点替换旧节点
                    patch = appendPatch(patch, {
                        type: PATCH.REPLACE,
                        vNode: newNode
                    })
                }
            } else if (isVText(newNode)) { // 既然新节点不是 VNode，就判断新节点是否是文本节点
                // 旧节点不是文本节点
                if (!isText(oldNode)) {
                    // 将旧节点替换成文本节点
                    patch = appendPatch(patch, {
                        type: PATCH.VTEXT,
                        vNode: newNode,
                    })
                } else if (newNode.text !== oldNode.text) { // 判断两者内容是否相等
                    // 替换文本
                    patch = appendPatch(patch, {
                        type: PATCH.VTEXT,
                        vNode: newNode,
                    })
                }

            }

            if (patch) {
                // 将补丁放入对应位置
                patches[index] = patch
            }

        }

        function diffProps(newProps, oldProps) {
            const patches = [];
            // 将新旧属性都浅拷贝进 props
            const props = Object.assign({}, newProps, oldProps)

            // 将props对象的键转换成数组
            Object.keys(props).forEach(key => {
                // 如果新属性里有这个键，就能获取到这个键的属性值
                const newVal = newProps[key];
                // 旧属性也一样
                const oldVal = newProps[key];
                // 新属性这个键不存在
                if (!newVal) {
                    // 那就直接用旧的
                    patches.push({
                        type: PATCH.REMOVE_PROP,
                        key,
                        value: oldVal,
                    })
                }
                // 旧的不存在或者新的不等于旧的
                if (oldVal === undefined || newVal !== oldVal) {
                    patches.push({
                        type: PATCH.SET_PROP,
                        key,
                        value: newVal,
                    })
                }
            })
        }

        function diffChildren(oldNode, newNode, patches, patch, index) {
            const oldChildren = oldNode.children;
            // 新节点按照旧节点的顺序重新排序
            const sortedSet = sortChildren(oldChildren, newNode.children)
            // 拿到新节点的子节点
            const newChildren = sortedSet.children;
            const oldLen = oldChildren.length;
            const newLen = newChildren.length;

            const len = oldLen > newLen ? oldLen : newLen
            for (let i = 0; i < len; i++) {
                let leftNode = oldChildren[i];
                let rightNode = newChildren[i];
                index++;

                // 调用前面写的方法
                dfsWalk(leftNode, rightNode, patches, index);

            }

            if (sortedSet.moves) {
                // 最后进行重新排序
                patch = appendPatch(patch, {
                    type: PATCH.ORDER,
                    moves: sortedSet.moves,
                })
            }

            return patch
        }
    </script>
</body>

</html>